/// \file instructiontranslator.cpp
/// \brief This file implements the logic to translate a PTC instruction in to
///        LLVM IR.

//
// This file is distributed under the MIT License. See LICENSE.md for details.
//

// Standard includes
#include <cassert>
#include <cstdint>
#include <fstream>
#include <queue>
#include <set>
#include <sstream>

// LLVM includes
#include "llvm/IR/CFG.h"
#include "llvm/IR/BasicBlock.h"
#include "llvm/IR/Intrinsics.h"
#include "llvm/IR/Module.h"
#include "llvm/Support/Casting.h"

// Local includes
#include "datastructures.h"
#include "instructiontranslator.h"
#include "ir-helpers.h"
#include "ptcinterface.h"
#include "rai.h"
#include "range.h"
#include "transformadapter.h"
#include "variablemanager.h"

using namespace llvm;

using IT = InstructionTranslator;

namespace PTC {

  template<bool C>
  class InstructionImpl;

  enum ArgumentType {
    In,
    Out,
    Const
  };

  template<ArgumentType Type, bool IsCall>
  class InstructionArgumentsIterator :
    public RandomAccessIterator<uint64_t,
                                InstructionArgumentsIterator<Type, IsCall>,
                                false> {
  public:
    using base = RandomAccessIterator<uint64_t,
                                      InstructionArgumentsIterator,
                                      false>;

    InstructionArgumentsIterator&
    operator=(const InstructionArgumentsIterator& r) {
      base::operator=(r);
      TheInstruction = r.TheInstruction;
      return *this;
    }


    InstructionArgumentsIterator(const InstructionArgumentsIterator& r) :
      base(r),
      TheInstruction(r.TheInstruction) { }

    InstructionArgumentsIterator(const InstructionArgumentsIterator& r,
                                 unsigned Index) :
      base(Index),
      TheInstruction(r.TheInstruction) { }

    InstructionArgumentsIterator(PTCInstruction *TheInstruction,
                                 unsigned Index) :
      base(Index),
      TheInstruction(TheInstruction) { }

    bool isCompatible(const InstructionArgumentsIterator& r) const {
      return TheInstruction == r.TheInstruction;
    }

  public:
    uint64_t get(unsigned Index) const;

  private:
    PTCInstruction *TheInstruction;
  };

  template<>
  inline uint64_t
  InstructionArgumentsIterator<In, true>::get(unsigned Index) const {
    return ptc_call_instruction_in_arg(&ptc, TheInstruction, Index);
  }

  template<>
  inline uint64_t
  InstructionArgumentsIterator<Const, true>::get(unsigned Index) const {
    return ptc_call_instruction_const_arg(&ptc, TheInstruction, Index);
  }

  template<>
  inline uint64_t
  InstructionArgumentsIterator<Out, true>::get(unsigned Index) const {
    return ptc_call_instruction_out_arg(&ptc, TheInstruction, Index);
  }

  template<>
  inline uint64_t
  InstructionArgumentsIterator<In, false>::get(unsigned Index) const {
    return ptc_instruction_in_arg(&ptc, TheInstruction, Index);
  }

  template<>
  inline uint64_t
  InstructionArgumentsIterator<Const, false>::get(unsigned Index) const {
    return ptc_instruction_const_arg(&ptc, TheInstruction, Index);
  }

  template<>
  inline uint64_t
  InstructionArgumentsIterator<Out, false>::get(unsigned Index) const {
    return ptc_instruction_out_arg(&ptc, TheInstruction, Index);
  }

  template<bool IsCall>
  class InstructionImpl {
  private:
    template<ArgumentType Type>
    using arguments = InstructionArgumentsIterator<Type, IsCall>;
  public:
    InstructionImpl(PTCInstruction *TheInstruction) :
      TheInstruction(TheInstruction),
      InArguments(arguments<In>(TheInstruction, 0),
                  arguments<In>(TheInstruction, inArgCount())),
      ConstArguments(arguments<Const>(TheInstruction, 0),
                     arguments<Const>(TheInstruction, constArgCount())),
      OutArguments(arguments<Out>(TheInstruction, 0),
                   arguments<Out>(TheInstruction, outArgCount()))
    { }

    PTCOpcode opcode() const {
      return TheInstruction->opc;
    }

    std::string helperName() const {
      assert(IsCall);
      PTCHelperDef *Helper = ptc_find_helper(&ptc, ConstArguments[0]);
      assert(Helper != nullptr && Helper->name != nullptr);
      return std::string(Helper->name);
    }

    uint64_t pc() const {
      assert(opcode() == PTC_INSTRUCTION_op_debug_insn_start);
      uint64_t PC = ConstArguments[0];
      if (ConstArguments.size() > 1)
        PC |= ConstArguments[1] << 32;
      return PC;
    }

  private:
    PTCInstruction* TheInstruction;

  public:
    const Range<InstructionArgumentsIterator<In, IsCall>> InArguments;
    const Range<InstructionArgumentsIterator<Const, IsCall>> ConstArguments;
    const Range<InstructionArgumentsIterator<Out, IsCall>> OutArguments;

  private:
    unsigned inArgCount() const;
    unsigned constArgCount() const;
    unsigned outArgCount() const;
  };

  using Instruction = InstructionImpl<false>;
  using CallInstruction = InstructionImpl<true>;

  template<>
  inline unsigned CallInstruction::inArgCount() const {
    return ptc_call_instruction_in_arg_count(&ptc, TheInstruction);
  }

  template<>
  inline unsigned Instruction::inArgCount() const {
    return ptc_instruction_in_arg_count(&ptc, TheInstruction);
  }

  template<>
  inline unsigned CallInstruction::constArgCount() const {
    return ptc_call_instruction_const_arg_count(&ptc, TheInstruction);
  }

  template<>
  inline unsigned Instruction::constArgCount() const {
    return ptc_instruction_const_arg_count(&ptc, TheInstruction);
  }

  template<>
  inline unsigned CallInstruction::outArgCount() const {
    return ptc_call_instruction_out_arg_count(&ptc, TheInstruction);
  }

  template<>
  inline unsigned Instruction::outArgCount() const {
    return ptc_instruction_out_arg_count(&ptc, TheInstruction);
  }

}

/// Converts a PTC condition into an LLVM predicate
///
/// \param Condition the input PTC condition.
///
/// \return the corresponding LLVM predicate.
static CmpInst::Predicate conditionToPredicate(PTCCondition Condition) {
  switch (Condition) {
  case PTC_COND_NEVER:
    // TODO: this is probably wrong
    return CmpInst::FCMP_FALSE;
  case PTC_COND_ALWAYS:
    // TODO: this is probably wrong
    return CmpInst::FCMP_TRUE;
  case PTC_COND_EQ:
    return CmpInst::ICMP_EQ;
  case PTC_COND_NE:
    return CmpInst::ICMP_NE;
  case PTC_COND_LT:
    return CmpInst::ICMP_SLT;
  case PTC_COND_GE:
    return CmpInst::ICMP_SGE;
  case PTC_COND_LE:
    return CmpInst::ICMP_SLE;
  case PTC_COND_GT:
    return CmpInst::ICMP_SGT;
  case PTC_COND_LTU:
    return CmpInst::ICMP_ULT;
  case PTC_COND_GEU:
    return CmpInst::ICMP_UGE;
  case PTC_COND_LEU:
    return CmpInst::ICMP_ULE;
  case PTC_COND_GTU:
    return CmpInst::ICMP_UGT;
  default:
    llvm_unreachable("Unknown comparison operator");
  }
}

/// Obtains the LLVM binary operation corresponding to the specified PTC opcode.
///
/// \param Opcode the PTC opcode.
///
/// \return the LLVM binary operation matching opcode.
static Instruction::BinaryOps opcodeToBinaryOp(PTCOpcode Opcode) {
  switch (Opcode) {
  case PTC_INSTRUCTION_op_add_i32:
  case PTC_INSTRUCTION_op_add_i64:
  case PTC_INSTRUCTION_op_add2_i32:
  case PTC_INSTRUCTION_op_add2_i64:
    return Instruction::Add;
  case PTC_INSTRUCTION_op_sub_i32:
  case PTC_INSTRUCTION_op_sub_i64:
  case PTC_INSTRUCTION_op_sub2_i32:
  case PTC_INSTRUCTION_op_sub2_i64:
    return Instruction::Sub;
  case PTC_INSTRUCTION_op_mul_i32:
  case PTC_INSTRUCTION_op_mul_i64:
    return Instruction::Mul;
  case PTC_INSTRUCTION_op_div_i32:
  case PTC_INSTRUCTION_op_div_i64:
    return Instruction::SDiv;
  case PTC_INSTRUCTION_op_divu_i32:
  case PTC_INSTRUCTION_op_divu_i64:
    return Instruction::UDiv;
  case PTC_INSTRUCTION_op_rem_i32:
  case PTC_INSTRUCTION_op_rem_i64:
    return Instruction::SRem;
  case PTC_INSTRUCTION_op_remu_i32:
  case PTC_INSTRUCTION_op_remu_i64:
    return Instruction::URem;
  case PTC_INSTRUCTION_op_and_i32:
  case PTC_INSTRUCTION_op_and_i64:
    return Instruction::And;
  case PTC_INSTRUCTION_op_or_i32:
  case PTC_INSTRUCTION_op_or_i64:
    return Instruction::Or;
  case PTC_INSTRUCTION_op_xor_i32:
  case PTC_INSTRUCTION_op_xor_i64:
    return Instruction::Xor;
  case PTC_INSTRUCTION_op_shl_i32:
  case PTC_INSTRUCTION_op_shl_i64:
    return Instruction::Shl;
  case PTC_INSTRUCTION_op_shr_i32:
  case PTC_INSTRUCTION_op_shr_i64:
    return Instruction::LShr;
  case PTC_INSTRUCTION_op_sar_i32:
  case PTC_INSTRUCTION_op_sar_i64:
    return Instruction::AShr;
  default:
    llvm_unreachable("PTC opcode is not a binary operator");
  }
}

/// Returns the maximum value which can be represented with the specified number
/// of bits.
static uint64_t getMaxValue(unsigned Bits) {
  if (Bits == 32)
    return 0xffffffff;
  else if (Bits == 64)
    return 0xffffffffffffffff;
  else
    llvm_unreachable("Not the number of bits in a integer type");
}

/// Maps an opcode the corresponding input and output register size.
///
/// \return the size, in bits, of the registers used by the opcode.
static unsigned getRegisterSize(unsigned Opcode) {
  switch (Opcode) {
  case PTC_INSTRUCTION_op_add2_i32:
  case PTC_INSTRUCTION_op_add_i32:
  case PTC_INSTRUCTION_op_andc_i32:
  case PTC_INSTRUCTION_op_and_i32:
  case PTC_INSTRUCTION_op_brcond2_i32:
  case PTC_INSTRUCTION_op_brcond_i32:
  case PTC_INSTRUCTION_op_bswap16_i32:
  case PTC_INSTRUCTION_op_bswap32_i32:
  case PTC_INSTRUCTION_op_deposit_i32:
  case PTC_INSTRUCTION_op_div2_i32:
  case PTC_INSTRUCTION_op_div_i32:
  case PTC_INSTRUCTION_op_divu2_i32:
  case PTC_INSTRUCTION_op_divu_i32:
  case PTC_INSTRUCTION_op_eqv_i32:
  case PTC_INSTRUCTION_op_ext16s_i32:
  case PTC_INSTRUCTION_op_ext16u_i32:
  case PTC_INSTRUCTION_op_ext8s_i32:
  case PTC_INSTRUCTION_op_ext8u_i32:
  case PTC_INSTRUCTION_op_ld16s_i32:
  case PTC_INSTRUCTION_op_ld16u_i32:
  case PTC_INSTRUCTION_op_ld8s_i32:
  case PTC_INSTRUCTION_op_ld8u_i32:
  case PTC_INSTRUCTION_op_ld_i32:
  case PTC_INSTRUCTION_op_movcond_i32:
  case PTC_INSTRUCTION_op_mov_i32:
  case PTC_INSTRUCTION_op_movi_i32:
  case PTC_INSTRUCTION_op_mul_i32:
  case PTC_INSTRUCTION_op_muls2_i32:
  case PTC_INSTRUCTION_op_mulsh_i32:
  case PTC_INSTRUCTION_op_mulu2_i32:
  case PTC_INSTRUCTION_op_muluh_i32:
  case PTC_INSTRUCTION_op_nand_i32:
  case PTC_INSTRUCTION_op_neg_i32:
  case PTC_INSTRUCTION_op_nor_i32:
  case PTC_INSTRUCTION_op_not_i32:
  case PTC_INSTRUCTION_op_orc_i32:
  case PTC_INSTRUCTION_op_or_i32:
  case PTC_INSTRUCTION_op_qemu_ld_i32:
  case PTC_INSTRUCTION_op_qemu_st_i32:
  case PTC_INSTRUCTION_op_rem_i32:
  case PTC_INSTRUCTION_op_remu_i32:
  case PTC_INSTRUCTION_op_rotl_i32:
  case PTC_INSTRUCTION_op_rotr_i32:
  case PTC_INSTRUCTION_op_sar_i32:
  case PTC_INSTRUCTION_op_setcond2_i32:
  case PTC_INSTRUCTION_op_setcond_i32:
  case PTC_INSTRUCTION_op_shl_i32:
  case PTC_INSTRUCTION_op_shr_i32:
  case PTC_INSTRUCTION_op_st16_i32:
  case PTC_INSTRUCTION_op_st8_i32:
  case PTC_INSTRUCTION_op_st_i32:
  case PTC_INSTRUCTION_op_sub2_i32:
  case PTC_INSTRUCTION_op_sub_i32:
  case PTC_INSTRUCTION_op_trunc_shr_i32:
  case PTC_INSTRUCTION_op_xor_i32:
    return 32;
  case PTC_INSTRUCTION_op_add2_i64:
  case PTC_INSTRUCTION_op_add_i64:
  case PTC_INSTRUCTION_op_andc_i64:
  case PTC_INSTRUCTION_op_and_i64:
  case PTC_INSTRUCTION_op_brcond_i64:
  case PTC_INSTRUCTION_op_bswap16_i64:
  case PTC_INSTRUCTION_op_bswap32_i64:
  case PTC_INSTRUCTION_op_bswap64_i64:
  case PTC_INSTRUCTION_op_deposit_i64:
  case PTC_INSTRUCTION_op_div2_i64:
  case PTC_INSTRUCTION_op_div_i64:
  case PTC_INSTRUCTION_op_divu2_i64:
  case PTC_INSTRUCTION_op_divu_i64:
  case PTC_INSTRUCTION_op_eqv_i64:
  case PTC_INSTRUCTION_op_ext16s_i64:
  case PTC_INSTRUCTION_op_ext16u_i64:
  case PTC_INSTRUCTION_op_ext32s_i64:
  case PTC_INSTRUCTION_op_ext32u_i64:
  case PTC_INSTRUCTION_op_ext8s_i64:
  case PTC_INSTRUCTION_op_ext8u_i64:
  case PTC_INSTRUCTION_op_ld16s_i64:
  case PTC_INSTRUCTION_op_ld16u_i64:
  case PTC_INSTRUCTION_op_ld32s_i64:
  case PTC_INSTRUCTION_op_ld32u_i64:
  case PTC_INSTRUCTION_op_ld8s_i64:
  case PTC_INSTRUCTION_op_ld8u_i64:
  case PTC_INSTRUCTION_op_ld_i64:
  case PTC_INSTRUCTION_op_movcond_i64:
  case PTC_INSTRUCTION_op_mov_i64:
  case PTC_INSTRUCTION_op_movi_i64:
  case PTC_INSTRUCTION_op_mul_i64:
  case PTC_INSTRUCTION_op_muls2_i64:
  case PTC_INSTRUCTION_op_mulsh_i64:
  case PTC_INSTRUCTION_op_mulu2_i64:
  case PTC_INSTRUCTION_op_muluh_i64:
  case PTC_INSTRUCTION_op_nand_i64:
  case PTC_INSTRUCTION_op_neg_i64:
  case PTC_INSTRUCTION_op_nor_i64:
  case PTC_INSTRUCTION_op_not_i64:
  case PTC_INSTRUCTION_op_orc_i64:
  case PTC_INSTRUCTION_op_or_i64:
  case PTC_INSTRUCTION_op_qemu_ld_i64:
  case PTC_INSTRUCTION_op_qemu_st_i64:
  case PTC_INSTRUCTION_op_rem_i64:
  case PTC_INSTRUCTION_op_remu_i64:
  case PTC_INSTRUCTION_op_rotl_i64:
  case PTC_INSTRUCTION_op_rotr_i64:
  case PTC_INSTRUCTION_op_sar_i64:
  case PTC_INSTRUCTION_op_setcond_i64:
  case PTC_INSTRUCTION_op_shl_i64:
  case PTC_INSTRUCTION_op_shr_i64:
  case PTC_INSTRUCTION_op_st16_i64:
  case PTC_INSTRUCTION_op_st32_i64:
  case PTC_INSTRUCTION_op_st8_i64:
  case PTC_INSTRUCTION_op_st_i64:
  case PTC_INSTRUCTION_op_sub2_i64:
  case PTC_INSTRUCTION_op_sub_i64:
  case PTC_INSTRUCTION_op_xor_i64:
    return 64;
  case PTC_INSTRUCTION_op_br:
  case PTC_INSTRUCTION_op_call:
  case PTC_INSTRUCTION_op_debug_insn_start:
  case PTC_INSTRUCTION_op_discard:
  case PTC_INSTRUCTION_op_exit_tb:
  case PTC_INSTRUCTION_op_goto_tb:
  case PTC_INSTRUCTION_op_set_label:
    return 0;
  default:
    llvm_unreachable("Unexpected opcode");
    break;
  }
}

/// Create a compare instruction given a comparison operator and the operands
///
/// \param Builder the builder to use to create the instruction.
/// \param RawCondition the PTC condition.
/// \param FirstOperand the first operand of the comparison.
/// \param SecondOperand the second operand of the comparison.
///
/// \return a compare instruction.
template<typename T>
static Value *CreateICmp(T& Builder,
                         uint64_t RawCondition,
                         Value *FirstOperand,
                         Value *SecondOperand) {
  PTCCondition Condition = static_cast<PTCCondition>(RawCondition);
  return Builder.CreateICmp(conditionToPredicate(Condition),
                            FirstOperand,
                            SecondOperand);
}

using LBM = IT::LabeledBlocksMap;
IT::InstructionTranslator(IRBuilder<>& Builder,
                          VariableManager& Variables,
                          JumpTargetManager& JumpTargets,
                          std::vector<BasicBlock *> Blocks,
                          const Architecture &SourceArchitecture,
                          const Architecture &TargetArchitecture) :
  Builder(Builder),
  Variables(Variables),
  JumpTargets(JumpTargets),
  Blocks(Blocks),
  TheModule(*Builder.GetInsertBlock()->getParent()->getParent()),
  TheFunction(Builder.GetInsertBlock()->getParent()),
  SourceArchitecture(SourceArchitecture),
  TargetArchitecture(TargetArchitecture),
  NewPCMarker(nullptr) {

  auto &Context = TheModule.getContext();
  using FT = FunctionType;
  // The newpc function call takes the following parameters:
  //
  // * address of the instruction
  // * instruction size
  // * isJT (-1: unknown, 0: no, 1: yes)
  // * all the local variables used by this instruction
  auto *NewPCMarkerTy = FT::get(Type::getVoidTy(Context),
                                {
                                  Type::getInt64Ty(Context),
                                  Type::getInt64Ty(Context),
                                  Type::getInt32Ty(Context)
                                },
                                true);
  NewPCMarker = Function::Create(NewPCMarkerTy,
                                 GlobalValue::ExternalLinkage,
                                 "newpc",
                                 &TheModule);
}

void IT::finalizeNewPCMarkers(std::string &CoveragePath) {
  std::ofstream Output(CoveragePath);

  Output << std::hex;
  for (User *U : NewPCMarker->users()) {
    auto *Call = cast<CallInst>(U);
    if (Call->getParent() != nullptr) {
      // Report the instruction on the coverage CSV
      using CI = ConstantInt;
      uint64_t PC = (cast<CI>(Call->getArgOperand(0)))->getLimitedValue();
      uint64_t Size = (cast<CI>(Call->getArgOperand(1)))->getLimitedValue();
      bool IsJT = JumpTargets.isJumpTarget(PC);
      Output << "0x" << PC
             << ",0x" << Size
             << "," << (IsJT ? "1" : "0")
             << std::endl;

      unsigned ArgCount = Call->getNumArgOperands();
      Call->setArgOperand(2, Builder.getInt32(static_cast<uint32_t>(IsJT)));

      // TODO: by default we should leave these
      for (unsigned I = 3; I < ArgCount - 1; I++)
        Call->setArgOperand(I, Call->getArgOperand(ArgCount - 1));
    }
  }
  Output << std::dec;
}

SmallSet<unsigned, 1> IT::preprocess(PTCInstructionList *InstructionList) {
  SmallSet<unsigned, 1> Result;

  for (unsigned I = 0; I < InstructionList->instruction_count; I++) {
    PTCInstruction &Instruction = InstructionList->instructions[I];
    switch (Instruction.opc) {
    case PTC_INSTRUCTION_op_movi_i32:
    case PTC_INSTRUCTION_op_movi_i64:
    case PTC_INSTRUCTION_op_mov_i32:
    case PTC_INSTRUCTION_op_mov_i64:
      break;
    default:
      continue;
    }

    const PTC::Instruction TheInstruction(&Instruction);
    unsigned OutArg = TheInstruction.OutArguments[0];
    PTCTemp *Temporary = ptc_temp_get(InstructionList, OutArg);

    if (!ptc_temp_is_global(InstructionList, OutArg))
      continue;

    if (0 != strcmp("btarget", Temporary->name))
      continue;

    for (unsigned J = I + 1; J < InstructionList->instruction_count; J++) {
      unsigned Opcode = InstructionList->instructions[J].opc;
      if (Opcode == PTC_INSTRUCTION_op_debug_insn_start)
        Result.insert(J);
    }

    break;
  }

  return Result;
}

std::tuple<IT::TranslationResult, MDNode *, uint64_t, uint64_t>
IT::newInstruction(PTCInstruction *Instr,
                   PTCInstruction *Next,
                   uint64_t EndPC,
                   bool IsFirst,
                   bool ForceNew) {
  using R = std::tuple<TranslationResult, MDNode *, uint64_t, uint64_t>;
  assert(Instr != nullptr);
  const PTC::Instruction TheInstruction(Instr);
  // A new original instruction, let's create a new metadata node
  // referencing it for all the next instructions to come
  uint64_t PC = TheInstruction.pc();
  uint64_t NextPC = Next != nullptr ?  PTC::Instruction(Next).pc() : EndPC;

  std::stringstream OriginalStringStream;
  disassembleOriginal(OriginalStringStream, PC);
  std::string OriginalString = OriginalStringStream.str();
  LLVMContext& Context = TheModule.getContext();
  MDString *MDOriginalString = MDString::get(Context, OriginalString);
  auto *MDPC = ConstantAsMetadata::get(Builder.getInt64(PC));
  MDNode *MDOriginalInstr = MDNode::getDistinct(Context,
                                                { MDOriginalString, MDPC });

  if (ForceNew)
    JumpTargets.registerJT(PC, JumpTargetManager::PostHelper);

  if (!IsFirst) {
    // Check if this PC already has a block and use it
    bool ShouldContinue;
    BasicBlock *DivergeTo = JumpTargets.newPC(PC, ShouldContinue);
    if (DivergeTo != nullptr) {
      Builder.CreateBr(DivergeTo);

      if (ShouldContinue) {
        // The block is empty, let's fill it
        Blocks.push_back(DivergeTo);
        Builder.SetInsertPoint(DivergeTo);
      } else {
        // The block contains already translated code, early exit
        return R { Stop, MDOriginalInstr, PC, NextPC };
      }
    }
  }

  Variables.newBasicBlock();

  // Insert a call to NewPCMarker capturing all the local temporaries
  // This prevents SROA from transforming them in SSA values, which is bad
  // in case we have to split a basic block
  std::vector<Value *> Args = {
    Builder.getInt64(PC),
    Builder.getInt64(NextPC - PC),
    Builder.getInt32(-1)
  };
  for (AllocaInst *Local : Variables.locals())
    Args.push_back(Local);

  auto *Call = Builder.CreateCall(NewPCMarker, Args);

  if (!IsFirst) {
    // Inform the JumpTargetManager about the new PC we met
    BasicBlock::iterator CurrentIt = Builder.GetInsertPoint();
    if (CurrentIt == Builder.GetInsertBlock()->begin())
      assert(JumpTargets.getBlockAt(PC) == Builder.GetInsertBlock());
    else
      JumpTargets.registerInstruction(PC, Call);
  }

  return R { Success, MDOriginalInstr, PC, NextPC };
}

static StoreInst *getLastUniqueWrite(BasicBlock *BB, Value *Register) {
  StoreInst *Result = nullptr;
  std::set<BasicBlock *> Visited;
  std::queue<BasicBlock *> WorkList;
  Visited.insert(BB);
  WorkList.push(BB);
  while (!WorkList.empty()) {
    BasicBlock *BB = WorkList.front();
    WorkList.pop();

    bool Stop = false;
    for (auto I = BB->rbegin(); I != BB->rend(); I++) {
      if (auto *Store = dyn_cast<StoreInst>(&*I)) {
        if (Store->getPointerOperand() == Register
            && isa<ConstantInt>(Store->getValueOperand())) {
          assert(Result == nullptr);
          Result = Store;
          Stop = true;
          break;
        }
      } else if (isa<CallInst>(&*I)) {
        Stop = true;
        break;
      }
    }

    if (!Stop) {
      for (BasicBlock *Prev : predecessors(BB)) {
        if (Visited.find(Prev) == Visited.end()) {
          WorkList.push(Prev);
          Visited.insert(BB);
        }
      }
    }
  }
  return Result;
}

IT::TranslationResult IT::translateCall(PTCInstruction *Instr) {
  const PTC::CallInstruction TheCall(Instr);

  std::vector<Value *> InArgs;

  for (uint64_t TemporaryId : TheCall.InArguments) {
    auto *Temporary = Variables.getOrCreate(TemporaryId, true);
    if (Temporary == nullptr)
      return Abort;
    auto *Load = Builder.CreateLoad(Temporary);
    Variables.setAliasScope(Load);
    InArgs.push_back(Load);
  }

  auto GetValueType = [] (Value *Argument) { return Argument->getType(); };
  std::vector<Type *> InArgsType = (InArgs | GetValueType).toVector();

  // TODO: handle multiple return arguments
  assert(TheCall.OutArguments.size() <= 1);

  Value *ResultDestination = nullptr;
  Type *ResultType = nullptr;

  if (TheCall.OutArguments.size() != 0) {
    ResultDestination = Variables.getOrCreate(TheCall.OutArguments[0], false);
    if (ResultDestination == nullptr)
      return Abort;
    ResultType = ResultDestination->getType()->getPointerElementType();
  } else {
    ResultType = Builder.getVoidTy();
  }

  auto *CalleeType = FunctionType::get(ResultType,
                                       ArrayRef<Type *>(InArgsType),
                                       false);

  std::string HelperName = "helper_" + TheCall.helperName();
  Constant *FunctionDeclaration = TheModule.getOrInsertFunction(HelperName,
                                                                CalleeType);

  StoreInst *PCSaver = getLastUniqueWrite(Builder.GetInsertBlock(),
                                          JumpTargets.pcReg());
  CallInst *Result = Builder.CreateCall(FunctionDeclaration, InArgs);

  if (TheCall.OutArguments.size() != 0) {
    auto *Store = Builder.CreateStore(Result, ResultDestination);
    Variables.setAliasScope(Store);
  }

  if (PCSaver != nullptr)
    return ForceNewPC;

  return Success;
}

IT::TranslationResult IT::translate(PTCInstruction *Instr,
                                    uint64_t PC,
                                    uint64_t NextPC) {
  const PTC::Instruction TheInstruction(Instr);

  std::vector<Value *> InArgs;
  for (uint64_t TemporaryId : TheInstruction.InArguments) {
    auto *Temporary = Variables.getOrCreate(TemporaryId, true);
    if (Temporary == nullptr)
      return Abort;

    auto *Load = Builder.CreateLoad(Temporary);
    Variables.setAliasScope(Load);
    InArgs.push_back(Load);
  }

  auto ConstArgs = TheInstruction.ConstArguments;
  LastPC = PC;
  auto Result = translateOpcode(TheInstruction.opcode(),
                                ConstArgs.toVector(),
                                InArgs);

  // Check if there was an error while translating the instruction
  if (!Result)
    return Abort;

  assert(Result->size() == (size_t) TheInstruction.OutArguments.size());
  // TODO: use ZipIterator here
  for (unsigned I = 0; I < Result->size(); I++) {
    auto *Destination = Variables.getOrCreate(TheInstruction.OutArguments[I],
                                              false);
    if (Destination == nullptr)
      return Abort;

    auto *Value = Result.get()[I];
    auto *Store = Builder.CreateStore(Value, Destination);
    Variables.setAliasScope(Store);

    // If we're writing the PC with an immediate, register it for exploration
    // immediately
    if (JumpTargets.isPCReg(Destination)) {
      auto *Constant = dyn_cast<ConstantInt>(Value);
      if (Constant != nullptr) {
        uint64_t Address = Constant->getLimitedValue();
        if (PC != Address)
          JumpTargets.registerJT(Address, JumpTargetManager::DirectJump);
      }
    }
  }

  return Success;
}

ErrorOr<std::vector<Value *>>
IT::translateOpcode(PTCOpcode Opcode,
                    std::vector<uint64_t> ConstArguments,
                    std::vector<Value *> InArguments) {
  LLVMContext& Context = TheModule.getContext();
  unsigned RegisterSize = getRegisterSize(Opcode);
  Type *RegisterType = nullptr;
  if (RegisterSize == 32)
    RegisterType = Builder.getInt32Ty();
  else if (RegisterSize == 64)
    RegisterType = Builder.getInt64Ty();
  else if (RegisterSize != 0)
    llvm_unreachable("Unexpected register size");

  using v = std::vector<Value *>;
  switch (Opcode) {
  case PTC_INSTRUCTION_op_movi_i32:
  case PTC_INSTRUCTION_op_movi_i64:
    return v { ConstantInt::get(RegisterType, ConstArguments[0]) };
  case PTC_INSTRUCTION_op_discard:
    // Let's overwrite the discarded temporary with a 0
    return v { ConstantInt::get(RegisterType, 0) };
  case PTC_INSTRUCTION_op_mov_i32:
  case PTC_INSTRUCTION_op_mov_i64:
    return v { Builder.CreateTrunc(InArguments[0], RegisterType) };
  case PTC_INSTRUCTION_op_setcond_i32:
  case PTC_INSTRUCTION_op_setcond_i64:
    {
      Value *Compare = CreateICmp(Builder,
                                  ConstArguments[0],
                                  InArguments[0],
                                  InArguments[1]);
      // TODO: convert single-bit registers to i1
      return v { Builder.CreateZExt(Compare, RegisterType) };
    }
  case PTC_INSTRUCTION_op_movcond_i32: // Resist the fallthrough temptation
  case PTC_INSTRUCTION_op_movcond_i64:
    {
      Value *Compare = CreateICmp(Builder,
                                  ConstArguments[0],
                                  InArguments[0],
                                  InArguments[1]);
      Value *Select = Builder.CreateSelect(Compare,
                                           InArguments[2],
                                           InArguments[3]);
      return v { Select };
    }
  case PTC_INSTRUCTION_op_qemu_ld_i32:
  case PTC_INSTRUCTION_op_qemu_ld_i64:
  case PTC_INSTRUCTION_op_qemu_st_i32:
  case PTC_INSTRUCTION_op_qemu_st_i64:
    {
      PTCLoadStoreArg MemoryAccess;
      MemoryAccess = ptc.parse_load_store_arg(ConstArguments[0]);

      // What are we supposed to do in this case?
      assert(MemoryAccess.access_type != PTC_MEMORY_ACCESS_UNKNOWN);

      unsigned Alignment = 0;
      if (MemoryAccess.access_type == PTC_MEMORY_ACCESS_UNALIGNED)
        Alignment = 1;
      else
        Alignment = SourceArchitecture.defaultAlignment();

      // Load size
      IntegerType *MemoryType = nullptr;
      switch (ptc_get_memory_access_size(MemoryAccess.type)) {
      case PTC_MO_8:
        MemoryType = Builder.getInt8Ty();
        break;
      case PTC_MO_16:
        MemoryType = Builder.getInt16Ty();
        break;
      case PTC_MO_32:
        MemoryType = Builder.getInt32Ty();
        break;
      case PTC_MO_64:
        MemoryType = Builder.getInt64Ty();
        break;
      default:
        llvm_unreachable("Unexpected load size");
      }

      // If necessary, handle endianess mismatch
      // TODO: it might be a bit overkill, but it be nice to make this function
      //       template-parametric w.r.t. endianess mismatch
      Function *BSwapFunction = nullptr;
      if (MemoryType != Builder.getInt8Ty() &&
          SourceArchitecture.endianess() != TargetArchitecture.endianess())
        BSwapFunction = Intrinsic::getDeclaration(&TheModule,
                                                  Intrinsic::bswap,
                                                  { MemoryType });

      bool SignExtend = ptc_is_sign_extended_load(MemoryAccess.type);

      Value *Pointer = nullptr;
      if (Opcode == PTC_INSTRUCTION_op_qemu_ld_i32 ||
          Opcode == PTC_INSTRUCTION_op_qemu_ld_i64) {

        Pointer = Builder.CreateIntToPtr(InArguments[0],
                                         MemoryType->getPointerTo());
        auto *Load = Builder.CreateAlignedLoad(Pointer, Alignment);
        Variables.setNoAlias(Load);
        Value *Loaded = Load;

        if (BSwapFunction != nullptr)
          Loaded = Builder.CreateCall(BSwapFunction, Load);

        if (SignExtend)
          return v { Builder.CreateSExt(Loaded, RegisterType) };
        else
          return v { Builder.CreateZExt(Loaded, RegisterType) };

      } else if (Opcode == PTC_INSTRUCTION_op_qemu_st_i32 ||
                 Opcode == PTC_INSTRUCTION_op_qemu_st_i64) {

        Pointer = Builder.CreateIntToPtr(InArguments[1],
                                         MemoryType->getPointerTo());
        Value *Value = Builder.CreateTrunc(InArguments[0], MemoryType);

        if (BSwapFunction != nullptr)
          Value = Builder.CreateCall(BSwapFunction, Value);

        auto *Store = Builder.CreateAlignedStore(Value, Pointer, Alignment);
        Variables.setNoAlias(Store);

        return v { };
      } else
        llvm_unreachable("Unknown load type");
    }
  case PTC_INSTRUCTION_op_ld8u_i32:
  case PTC_INSTRUCTION_op_ld8s_i32:
  case PTC_INSTRUCTION_op_ld16u_i32:
  case PTC_INSTRUCTION_op_ld16s_i32:
  case PTC_INSTRUCTION_op_ld_i32:
  case PTC_INSTRUCTION_op_ld8u_i64:
  case PTC_INSTRUCTION_op_ld8s_i64:
  case PTC_INSTRUCTION_op_ld16u_i64:
  case PTC_INSTRUCTION_op_ld16s_i64:
  case PTC_INSTRUCTION_op_ld32u_i64:
  case PTC_INSTRUCTION_op_ld32s_i64:
  case PTC_INSTRUCTION_op_ld_i64:
    {
      Value *Base = dyn_cast<LoadInst>(InArguments[0])->getPointerOperand();
      if (Base == nullptr || !Variables.isEnv(Base)) {
        // TODO: emit warning
        return std::errc::invalid_argument;
      }

      bool Signed;
      switch (Opcode) {
      case PTC_INSTRUCTION_op_ld_i32:
      case PTC_INSTRUCTION_op_ld_i64:

      case PTC_INSTRUCTION_op_ld8u_i32:
      case PTC_INSTRUCTION_op_ld16u_i32:
      case PTC_INSTRUCTION_op_ld8u_i64:
      case PTC_INSTRUCTION_op_ld16u_i64:
      case PTC_INSTRUCTION_op_ld32u_i64:
        Signed = false;
        break;
      case PTC_INSTRUCTION_op_ld8s_i32:
      case PTC_INSTRUCTION_op_ld16s_i32:
      case PTC_INSTRUCTION_op_ld8s_i64:
      case PTC_INSTRUCTION_op_ld16s_i64:
      case PTC_INSTRUCTION_op_ld32s_i64:
        Signed = true;
        break;
      default:
        llvm_unreachable("Unexpected opcode");
      }

      unsigned LoadSize;
      switch (Opcode) {
      case PTC_INSTRUCTION_op_ld8u_i32:
      case PTC_INSTRUCTION_op_ld8s_i32:
      case PTC_INSTRUCTION_op_ld8u_i64:
      case PTC_INSTRUCTION_op_ld8s_i64:
        LoadSize = 1;
        break;
      case PTC_INSTRUCTION_op_ld16u_i32:
      case PTC_INSTRUCTION_op_ld16s_i32:
      case PTC_INSTRUCTION_op_ld16u_i64:
      case PTC_INSTRUCTION_op_ld16s_i64:
        LoadSize = 2;
        break;
      case PTC_INSTRUCTION_op_ld_i32:
      case PTC_INSTRUCTION_op_ld32u_i64:
      case PTC_INSTRUCTION_op_ld32s_i64:
        LoadSize = 4;
        break;
      case PTC_INSTRUCTION_op_ld_i64:
        LoadSize = 8;
        break;
      default:
        llvm_unreachable("Unexpected opcode");
      }

      Value *Result = Variables.loadFromEnvOffset(Builder,
                                                  LoadSize,
                                                  ConstArguments[0]);
      assert(Result != nullptr);

      // Zero/sign extend in the target dimension
      if (Signed)
        return v { Builder.CreateSExt(Result, RegisterType) };
      else
        return v { Builder.CreateZExt(Result, RegisterType) };

    }
  case PTC_INSTRUCTION_op_st8_i32:
  case PTC_INSTRUCTION_op_st16_i32:
  case PTC_INSTRUCTION_op_st_i32:
  case PTC_INSTRUCTION_op_st8_i64:
  case PTC_INSTRUCTION_op_st16_i64:
  case PTC_INSTRUCTION_op_st32_i64:
  case PTC_INSTRUCTION_op_st_i64:
    {
      unsigned StoreSize;
      switch (Opcode) {
      case PTC_INSTRUCTION_op_st8_i32:
      case PTC_INSTRUCTION_op_st8_i64:
        StoreSize = 1;
        break;
      case PTC_INSTRUCTION_op_st16_i32:
      case PTC_INSTRUCTION_op_st16_i64:
        StoreSize = 2;
        break;
      case PTC_INSTRUCTION_op_st_i32:
      case PTC_INSTRUCTION_op_st32_i64:
        StoreSize = 4;
        break;
      case PTC_INSTRUCTION_op_st_i64:
        StoreSize = 8;
        break;
      default:
        llvm_unreachable("Unexpected opcode");
      }

      Value *Base = dyn_cast<LoadInst>(InArguments[1])->getPointerOperand();
      if (Base == nullptr || !Variables.isEnv(Base)) {
        // TODO: emit warning
        return std::errc::invalid_argument;
      }

      bool Result = Variables.storeToEnvOffset(Builder,
                                               StoreSize,
                                               ConstArguments[0],
                                               InArguments[0]);
      assert(Result);
      (void) Result;

      return v { };
    }
  case PTC_INSTRUCTION_op_add_i32:
  case PTC_INSTRUCTION_op_sub_i32:
  case PTC_INSTRUCTION_op_mul_i32:
  case PTC_INSTRUCTION_op_div_i32:
  case PTC_INSTRUCTION_op_divu_i32:
  case PTC_INSTRUCTION_op_rem_i32:
  case PTC_INSTRUCTION_op_remu_i32:
  case PTC_INSTRUCTION_op_and_i32:
  case PTC_INSTRUCTION_op_or_i32:
  case PTC_INSTRUCTION_op_xor_i32:
  case PTC_INSTRUCTION_op_shl_i32:
  case PTC_INSTRUCTION_op_shr_i32:
  case PTC_INSTRUCTION_op_sar_i32:
  case PTC_INSTRUCTION_op_add_i64:
  case PTC_INSTRUCTION_op_sub_i64:
  case PTC_INSTRUCTION_op_mul_i64:
  case PTC_INSTRUCTION_op_div_i64:
  case PTC_INSTRUCTION_op_divu_i64:
  case PTC_INSTRUCTION_op_rem_i64:
  case PTC_INSTRUCTION_op_remu_i64:
  case PTC_INSTRUCTION_op_and_i64:
  case PTC_INSTRUCTION_op_or_i64:
  case PTC_INSTRUCTION_op_xor_i64:
  case PTC_INSTRUCTION_op_shl_i64:
  case PTC_INSTRUCTION_op_shr_i64:
  case PTC_INSTRUCTION_op_sar_i64:
    {
      // TODO: assert on sizes?
      Instruction::BinaryOps BinaryOp = opcodeToBinaryOp(Opcode);
      Value *Operation = Builder.CreateBinOp(BinaryOp,
                                             InArguments[0],
                                             InArguments[1]);
      return v { Operation };
    }
  case PTC_INSTRUCTION_op_div2_i32:
  case PTC_INSTRUCTION_op_divu2_i32:
  case PTC_INSTRUCTION_op_div2_i64:
  case PTC_INSTRUCTION_op_divu2_i64:
    {
      Instruction::BinaryOps DivisionOp, RemainderOp;

      if (Opcode == PTC_INSTRUCTION_op_div2_i32 ||
          Opcode == PTC_INSTRUCTION_op_div2_i64) {
        DivisionOp = Instruction::SDiv;
        RemainderOp = Instruction::SRem;
      } else if (Opcode == PTC_INSTRUCTION_op_divu2_i32 ||
                 Opcode == PTC_INSTRUCTION_op_divu2_i64) {
        DivisionOp = Instruction::UDiv;
        RemainderOp = Instruction::URem;
      } else
        llvm_unreachable("Unknown operation type");

      // TODO: we're ignoring InArguments[1], which is the MSB
      // TODO: assert on sizes?
      Value *Division = Builder.CreateBinOp(DivisionOp,
                                            InArguments[0],
                                            InArguments[2]);
      Value *Remainder = Builder.CreateBinOp(RemainderOp,
                                             InArguments[0],
                                             InArguments[2]);
      return v { Division, Remainder };
    }
  case PTC_INSTRUCTION_op_rotr_i32:
  case PTC_INSTRUCTION_op_rotr_i64:
  case PTC_INSTRUCTION_op_rotl_i32:
  case PTC_INSTRUCTION_op_rotl_i64:
    {
      Value *Bits = ConstantInt::get(RegisterType, RegisterSize);

      Instruction::BinaryOps FirstShiftOp, SecondShiftOp;
      if (Opcode == PTC_INSTRUCTION_op_rotl_i32 ||
          Opcode == PTC_INSTRUCTION_op_rotl_i64) {
        FirstShiftOp = Instruction::Shl;
        SecondShiftOp = Instruction::LShr;
      } else if (Opcode == PTC_INSTRUCTION_op_rotr_i32 ||
                 Opcode == PTC_INSTRUCTION_op_rotr_i64) {
        FirstShiftOp = Instruction::LShr;
        SecondShiftOp = Instruction::Shl;
      } else
        llvm_unreachable("Unexpected opcode");

      Value *FirstShift = Builder.CreateBinOp(FirstShiftOp,
                                              InArguments[0],
                                              InArguments[1]);
      Value *SecondShiftAmount = Builder.CreateSub(Bits,
                                                   InArguments[1]);
      Value *SecondShift = Builder.CreateBinOp(SecondShiftOp,
                                               InArguments[0],
                                               SecondShiftAmount);

      return v { Builder.CreateOr(FirstShift, SecondShift) };
    }
  case PTC_INSTRUCTION_op_deposit_i32:
  case PTC_INSTRUCTION_op_deposit_i64:
    {
      unsigned Position = ConstArguments[0];
      if (Position == RegisterSize)
        return v { InArguments[0] };

      unsigned Length = ConstArguments[1];
      uint64_t Bits = 0;

      // Thou shall not << 32
      if (Length == RegisterSize)
        Bits = getMaxValue(RegisterSize);
      else
        Bits = (1 << Length) - 1;

      // result = (t1 & ~(bits << position)) | ((t2 & bits) << position)
      uint64_t BaseMask = ~(Bits << Position);
      Value *MaskedBase = Builder.CreateAnd(InArguments[0], BaseMask);
      Value *Deposit = Builder.CreateAnd(InArguments[1], Bits);
      Value *ShiftedDeposit = Builder.CreateShl(Deposit, Position);
      Value *Result = Builder.CreateOr(MaskedBase, ShiftedDeposit);

      return v { Result };
    }
  case PTC_INSTRUCTION_op_ext8s_i32:
  case PTC_INSTRUCTION_op_ext16s_i32:
  case PTC_INSTRUCTION_op_ext8u_i32:
  case PTC_INSTRUCTION_op_ext16u_i32:
  case PTC_INSTRUCTION_op_ext8s_i64:
  case PTC_INSTRUCTION_op_ext16s_i64:
  case PTC_INSTRUCTION_op_ext32s_i64:
  case PTC_INSTRUCTION_op_ext8u_i64:
  case PTC_INSTRUCTION_op_ext16u_i64:
  case PTC_INSTRUCTION_op_ext32u_i64:
    {
      Type *SourceType = nullptr;
      switch (Opcode) {
      case PTC_INSTRUCTION_op_ext8s_i32:
      case PTC_INSTRUCTION_op_ext8u_i32:
      case PTC_INSTRUCTION_op_ext8s_i64:
      case PTC_INSTRUCTION_op_ext8u_i64:
        SourceType = Builder.getInt8Ty();
        break;
      case PTC_INSTRUCTION_op_ext16s_i32:
      case PTC_INSTRUCTION_op_ext16u_i32:
      case PTC_INSTRUCTION_op_ext16s_i64:
      case PTC_INSTRUCTION_op_ext16u_i64:
        SourceType = Builder.getInt16Ty();
        break;
      case PTC_INSTRUCTION_op_ext32s_i64:
      case PTC_INSTRUCTION_op_ext32u_i64:
        SourceType = Builder.getInt32Ty();
        break;
      default:
        llvm_unreachable("Unexpected opcode");
      }

      Value *Truncated = Builder.CreateTrunc(InArguments[0], SourceType);

      switch (Opcode) {
      case PTC_INSTRUCTION_op_ext8s_i32:
      case PTC_INSTRUCTION_op_ext8s_i64:
      case PTC_INSTRUCTION_op_ext16s_i32:
      case PTC_INSTRUCTION_op_ext16s_i64:
      case PTC_INSTRUCTION_op_ext32s_i64:
        return v { Builder.CreateSExt(Truncated, RegisterType) };
      case PTC_INSTRUCTION_op_ext8u_i32:
      case PTC_INSTRUCTION_op_ext8u_i64:
      case PTC_INSTRUCTION_op_ext16u_i32:
      case PTC_INSTRUCTION_op_ext16u_i64:
      case PTC_INSTRUCTION_op_ext32u_i64:
        return v { Builder.CreateZExt(Truncated, RegisterType) };
      default:
        llvm_unreachable("Unexpected opcode");
      }
    }
  case PTC_INSTRUCTION_op_not_i32:
  case PTC_INSTRUCTION_op_not_i64:
    return v { Builder.CreateXor(InArguments[0], getMaxValue(RegisterSize)) };
  case PTC_INSTRUCTION_op_neg_i32:
  case PTC_INSTRUCTION_op_neg_i64:
    {
      auto *InitialValue = ConstantInt::get(RegisterType, 0);
      return v { Builder.CreateSub(InitialValue, InArguments[0]) };
    }
  case PTC_INSTRUCTION_op_andc_i32:
  case PTC_INSTRUCTION_op_andc_i64:
  case PTC_INSTRUCTION_op_orc_i32:
  case PTC_INSTRUCTION_op_orc_i64:
  case PTC_INSTRUCTION_op_eqv_i32:
  case PTC_INSTRUCTION_op_eqv_i64:
    {
      Instruction::BinaryOps ExternalOp;
      switch (Opcode) {
      case PTC_INSTRUCTION_op_andc_i32:
      case PTC_INSTRUCTION_op_andc_i64:
        ExternalOp = Instruction::And;
        break;
      case PTC_INSTRUCTION_op_orc_i32:
      case PTC_INSTRUCTION_op_orc_i64:
        ExternalOp = Instruction::Or;
        break;
      case PTC_INSTRUCTION_op_eqv_i32:
      case PTC_INSTRUCTION_op_eqv_i64:
        ExternalOp = Instruction::Xor;
        break;
      default:
        llvm_unreachable("Unexpected opcode");
      }

      Value *Negate = Builder.CreateXor(InArguments[1],
                                        getMaxValue(RegisterSize));
      Value *Result = Builder.CreateBinOp(ExternalOp, InArguments[0], Negate);
      return v { Result };
    }
  case PTC_INSTRUCTION_op_nand_i32:
  case PTC_INSTRUCTION_op_nand_i64:
    {
      Value *AndValue = Builder.CreateAnd(InArguments[0], InArguments[1]);
      Value *Result = Builder.CreateXor(AndValue, getMaxValue(RegisterSize));
      return v { Result };
    }
  case PTC_INSTRUCTION_op_nor_i32:
  case PTC_INSTRUCTION_op_nor_i64:
    {
      Value *OrValue = Builder.CreateOr(InArguments[0], InArguments[1]);
      Value *Result = Builder.CreateXor(OrValue, getMaxValue(RegisterSize));
      return v { Result };
    }
  case PTC_INSTRUCTION_op_bswap16_i32:
  case PTC_INSTRUCTION_op_bswap32_i32:
  case PTC_INSTRUCTION_op_bswap16_i64:
  case PTC_INSTRUCTION_op_bswap32_i64:
  case PTC_INSTRUCTION_op_bswap64_i64:
    {
      Type *SwapType = nullptr;
      switch (Opcode) {
      case PTC_INSTRUCTION_op_bswap16_i32:
      case PTC_INSTRUCTION_op_bswap16_i64:
        SwapType = Builder.getInt16Ty();
        break;
      case PTC_INSTRUCTION_op_bswap32_i32:
      case PTC_INSTRUCTION_op_bswap32_i64:
        SwapType = Builder.getInt32Ty();
        break;
      case PTC_INSTRUCTION_op_bswap64_i64:
        SwapType = Builder.getInt64Ty();
        break;
      default:
        llvm_unreachable("Unexpected opcode");
      }

      Value *Truncated = Builder.CreateTrunc(InArguments[0], SwapType);

      Function *BSwapFunction = Intrinsic::getDeclaration(&TheModule,
                                                          Intrinsic::bswap,
                                                          { SwapType });
      Value *Swapped = Builder.CreateCall(BSwapFunction, Truncated);

      return v { Builder.CreateZExt(Swapped, RegisterType) };
    }
  case PTC_INSTRUCTION_op_set_label:
    {
      unsigned LabelId = ptc.get_arg_label_id(ConstArguments[0]);

      std::stringstream LabelSS;
      LabelSS << "bb." << JumpTargets.nameForAddress(LastPC);
      LabelSS << "_L" << std::dec << LabelId;
      std::string Label = LabelSS.str();

      BasicBlock *Fallthrough = nullptr;
      auto ExistingBasicBlock = LabeledBasicBlocks.find(Label);

      if (ExistingBasicBlock == LabeledBasicBlocks.end()) {
        Fallthrough = BasicBlock::Create(Context, Label, TheFunction);
        Fallthrough->moveAfter(Builder.GetInsertBlock());
        LabeledBasicBlocks[Label] = Fallthrough;
      } else {
        // A basic block with that label already exist
        Fallthrough = LabeledBasicBlocks[Label];

        // Ensure it's empty
        assert(Fallthrough->begin() == Fallthrough->end());

        // Move it to the bottom
        Fallthrough->removeFromParent();
        TheFunction->getBasicBlockList().push_back(Fallthrough);
      }

      Builder.CreateBr(Fallthrough);

      Blocks.push_back(Fallthrough);
      Builder.SetInsertPoint(Fallthrough);
      Variables.newBasicBlock();

      return v { };
    }
  case PTC_INSTRUCTION_op_br:
  case PTC_INSTRUCTION_op_brcond_i32:
  case PTC_INSTRUCTION_op_brcond2_i32:
  case PTC_INSTRUCTION_op_brcond_i64:
    {
      // We take the last constant arguments, which is the LabelId both in
      // conditional and unconditional jumps
      unsigned LabelId = ptc.get_arg_label_id(ConstArguments.back());

      std::stringstream LabelSS;
      LabelSS << "bb." << JumpTargets.nameForAddress(LastPC);
      LabelSS << "_L" << std::dec << LabelId;
      std::string Label = LabelSS.str();

      BasicBlock *Fallthrough = BasicBlock::Create(Context,
                                                   Label + "_ft",
                                                   TheFunction);

      // Look for a matching label
      BasicBlock *Target = nullptr;
      auto ExistingBasicBlock = LabeledBasicBlocks.find(Label);

      // No matching label, create a temporary block
      if (ExistingBasicBlock == LabeledBasicBlocks.end()) {
        Target = BasicBlock::Create(Context, Label, TheFunction);
        LabeledBasicBlocks[Label] = Target;
      } else
        Target = LabeledBasicBlocks[Label];

      if (Opcode == PTC_INSTRUCTION_op_br) {
        // Unconditional jump
        Builder.CreateBr(Target);
      } else if (Opcode == PTC_INSTRUCTION_op_brcond_i32
                 || Opcode == PTC_INSTRUCTION_op_brcond_i64) {
        // Conditional jump
        Value *Compare = CreateICmp(Builder,
                                    ConstArguments[0],
                                    InArguments[0],
                                    InArguments[1]);
        Builder.CreateCondBr(Compare, Target, Fallthrough);
      } else
        llvm_unreachable("Unhandled opcode");

      Blocks.push_back(Fallthrough);
      Builder.SetInsertPoint(Fallthrough);
      Variables.newBasicBlock();

      return v { };
    }
  case PTC_INSTRUCTION_op_exit_tb:
    {
      auto *Zero = ConstantInt::get(Type::getInt32Ty(Context), 0);
      Builder.CreateCall(JumpTargets.exitTB(), { Zero });
      Builder.CreateUnreachable();

      auto *NextBB = BasicBlock::Create(Context, "", TheFunction);
      Blocks.push_back(NextBB);
      Builder.SetInsertPoint(NextBB);
      Variables.newBasicBlock();

      return v { };
    }
  case PTC_INSTRUCTION_op_goto_tb:
    // Nothing to do here
    return v { };
  case PTC_INSTRUCTION_op_add2_i32:
  case PTC_INSTRUCTION_op_sub2_i32:
  case PTC_INSTRUCTION_op_add2_i64:
  case PTC_INSTRUCTION_op_sub2_i64:
    {
      Value *FirstOpLow = nullptr;
      Value *FirstOpHigh = nullptr;
      Value *SecondOpLow = nullptr;
      Value *SecondOpHigh = nullptr;

      IntegerType *DestinationType = Builder.getIntNTy(RegisterSize * 2);

      FirstOpLow = Builder.CreateZExt(InArguments[0], DestinationType);
      FirstOpHigh = Builder.CreateZExt(InArguments[1], DestinationType);
      SecondOpLow = Builder.CreateZExt(InArguments[2], DestinationType);
      SecondOpHigh = Builder.CreateZExt(InArguments[3], DestinationType);

      FirstOpHigh = Builder.CreateShl(FirstOpHigh, RegisterSize);
      SecondOpHigh = Builder.CreateShl(SecondOpHigh, RegisterSize);

      Value *FirstOp = Builder.CreateOr(FirstOpHigh, FirstOpLow);
      Value *SecondOp = Builder.CreateOr(SecondOpHigh,
                                              SecondOpLow);

      Instruction::BinaryOps BinaryOp = opcodeToBinaryOp(Opcode);

      Value *Result = Builder.CreateBinOp(BinaryOp, FirstOp, SecondOp);

      Value *ResultLow = Builder.CreateTrunc(Result, RegisterType);
      Value *ShiftedResult = Builder.CreateLShr(Result, RegisterSize);
      Value *ResultHigh = Builder.CreateTrunc(ShiftedResult, RegisterType);

      return v { ResultLow, ResultHigh };
    }
  case PTC_INSTRUCTION_op_mulu2_i32:
  case PTC_INSTRUCTION_op_mulu2_i64:
  case PTC_INSTRUCTION_op_muls2_i32:
  case PTC_INSTRUCTION_op_muls2_i64:
    {
      IntegerType *DestinationType = Builder.getIntNTy(RegisterSize * 2);

      Value *FirstOp = nullptr;
      Value *SecondOp = nullptr;

      if (Opcode == PTC_INSTRUCTION_op_mulu2_i32
          || Opcode == PTC_INSTRUCTION_op_mulu2_i64) {
        FirstOp = Builder.CreateZExt(InArguments[0], DestinationType);
        SecondOp = Builder.CreateZExt(InArguments[1], DestinationType);
      } else if (Opcode == PTC_INSTRUCTION_op_muls2_i32
                 || Opcode == PTC_INSTRUCTION_op_muls2_i64) {
        FirstOp = Builder.CreateSExt(InArguments[0], DestinationType);
        SecondOp = Builder.CreateSExt(InArguments[1], DestinationType);
      } else
        llvm_unreachable("Unexpected opcode");

      Value *Result = Builder.CreateMul(FirstOp, SecondOp);

      Value *ResultLow = Builder.CreateTrunc(Result, RegisterType);
      Value *ShiftedResult = Builder.CreateLShr(Result, RegisterSize);
      Value *ResultHigh = Builder.CreateTrunc(ShiftedResult, RegisterType);

      return v { ResultLow, ResultHigh };
    }
  case PTC_INSTRUCTION_op_muluh_i32:
  case PTC_INSTRUCTION_op_mulsh_i32:
  case PTC_INSTRUCTION_op_muluh_i64:
  case PTC_INSTRUCTION_op_mulsh_i64:

  case PTC_INSTRUCTION_op_setcond2_i32:

  case PTC_INSTRUCTION_op_trunc_shr_i32:
    llvm_unreachable("Instruction not implemented");
  default:
    llvm_unreachable("Unknown opcode");
  }
}
